﻿#region Includes
using System;
using System.Collections.Generic;

using GingerGL;
using GingerGL.Math;

using OpenTK;
using OpenTK.Graphics.OpenGL;
#endregion

namespace GingerGL.Graphics
{
    //------------------------------------------------------------//
    /// <summary>
    /// Mesh Structure
    /// </summary>
    public class Mesh : IRenderable
    {
        //--------------------------------------------------------//
        #region Variables
        /// <summary>
        /// Gets Geometry Buffer
        /// </summary>
        public GeometryBuffer Buffer { get; protected set; }    //geometry buffer
        /// <summary>
        /// Gets Vertex Begin Mode
        /// </summary>
        public BeginMode Mode { get; private set; }             //need to know how to draw
        /// <summary>
        /// Gets Which elements of the vertex that were used
        /// </summary>
        public VertexUsage VertexUsage { get; private set; }    //which vertices are used
        /// <summary>
        /// Texture Array
        /// </summary>
        public List<Material> Materials { get; set; }           //texture array

        public const int Texture0 = 0;
        public const int Texture1 = 1;
        #endregion
        //--------------------------------------------------------//
        #region Properties
        /// <summary>
        /// Gets/Set Objects Visibility
        /// </summary>
        public bool Visible { get; set; }
        /// <summary>
        /// Get the Elements in the Index buffer
        /// </summary>
        public int NumOfElements { get { return Buffer.IndexBuffer.Count; } }
        /// <summary>
        /// Get the Elements in the Index buffer
        /// </summary>
        public bool IsCommited { get { return Buffer.IndexBuffer.IsCommited == true && Buffer.VertexBuffer.IsCommited == true; } }
        #endregion
        //--------------------------------------------------------//
        #region Constructors
        /// <summary>
        /// Initialize a new instance of object
        /// </summary>
        public Mesh(BeginMode mode, VertexUsage usage)
        { 
            this.Mode = mode;
            this.Materials = new List<Material>();
            this.VertexUsage = usage;
            this.Buffer = new GeometryBuffer();
        }
        /// <summary>
        /// Initialize a new instance of object
        /// </summary>
        /// <param name="mode">Primitive Draw Mode</param>
        public Mesh(BeginMode mode) : this(mode, VertexUsage.Position & VertexUsage.Texture0) { }
        #endregion
        //--------------------------------------------------------//
        #region Methods
        /// <summary>
        /// Invalidates Mesh
        /// </summary>
        public void Invalidate()
        {
            Buffer.Invalidate();
            foreach (Material m in Materials)
                m.Invalidate();
        }
        /// <summary>
        /// Executes drawing this mesh
        /// </summary>
        public virtual void Draw()
        {
            if (!IsCommited) Buffer.Commit();                                           //load buffers
            GL.PushAttrib(AttribMask.EnableBit);                                        //save enables
            GL.PushClientAttrib(ClientAttribMask.ClientVertexArrayBit);                 //save current array state
            //----------------------------------------------------//
            #region Vertex Buffer
            GL.BindBuffer(BufferTarget.ArrayBuffer, Buffer.VertexBuffer.VertexBufferId);//bind to the array buffer id
            GL.EnableClientState(ArrayCap.VertexArray);                                 //enable the client state so it will use this array buffer pointer
            GL.VertexPointer                                                            //set the Pointer to the current bound array describing how the data ia stored
            (
                3,
                VertexPointerType.Float,
                Vertex.SizeInBytes,
                Vertex.Offset(VertexOffset.Position)
            );
            #endregion
            //----------------------------------------------------//
            #region Normals
            if ((VertexUsage & VertexUsage.Normal) == VertexUsage.Normal)               //do we need to use normals?
            {
                GL.EnableClientState(ArrayCap.NormalArray);                             // Enable the client state so it will use this array buffer pointer
                GL.NormalPointer(                                                       //set the Pointer to the current bound array describing how the data ia stored
                    NormalPointerType.Float,
                    Vertex.SizeInBytes,
                    Vertex.Offset(VertexOffset.Normal));
            }
            #endregion
            //----------------------------------------------------//
            #region Color
            if ((VertexUsage & VertexUsage.Color) == VertexUsage.Color)                 //do we use colors
            {
                GL.EnableClientState(ArrayCap.ColorArray);                              // Enable the client state so it will use this array buffer pointer
                GL.ColorPointer                                                         // Set the Pointer to the current bound array describing how the data ia stored
                (
                    4,
                    ColorPointerType.Float,
                    Vertex.SizeInBytes,
                    Vertex.Offset(VertexOffset.Color)
                );
            }
            #endregion
            //----------------------------------------------------//
            #region Textures
            //----------------------------------------------------//
            #region Texture1
            if ((VertexUsage & VertexUsage.Texture1) == VertexUsage.Texture1)                //Texture1
            {
                GL.ClientActiveTexture(TextureUnit.Texture1);                           //set the active state to texture1
                GL.EnableClientState(ArrayCap.TextureCoordArray);                       //enable tex coords
                GL.TexCoordPointer(                                                     //create point for this texture
                    2,
                    TexCoordPointerType.Float,
                    Vertex.SizeInBytes,
                    Vertex.Offset(VertexOffset.Texture1));
                Materials[Texture1].Apply();
            }
            #endregion
            //----------------------------------------------------//
            #region Texture0
            if ((VertexUsage & VertexUsage.Texture0) == VertexUsage.Texture0)           //Texture0
            {
                GL.ClientActiveTexture(TextureUnit.Texture0);                           //set the active state to texture0
                GL.EnableClientState(ArrayCap.TextureCoordArray);                       //enable tex coords
                GL.TexCoordPointer(                                                     //create point for this texture
                    2,
                    TexCoordPointerType.Float,
                    Vertex.SizeInBytes,
                    Vertex.Offset(VertexOffset.Texture0));
                Materials[Texture0].Apply();                                            //bind textures
            }
            #endregion
            //----------------------------------------------------//
            #endregion
            //----------------------------------------------------//
            #region Elements
            // Element Array Buffer
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, Buffer.IndexBuffer.IndexBufferId);// Bind to the Array Buffer ID
            #endregion
            //----------------------------------------------------//
            // Draw the elements in the element array buffer
            // Draws up items in the Color, Vertex, TexCoordinate, and Normal Buffers using indices in the ElementArrayBuffer
            GL.DrawElements                                                             //draw buffers
            (
                Mode,
                NumOfElements,
                DrawElementsType.UnsignedInt,
                IntPtr.Zero
            );
            GL.PopClientAttrib();                                                       // Restore the state
            GL.PopAttrib();                                                             //disable attributes
        }
        /// <summary>
        /// Disposes Item
        /// </summary>
        public void Dispose() { Invalidate(); }
        #endregion
        //--------------------------------------------------------//
    }
    //------------------------------------------------------------//
}
